package com.gitee.xqxyxchy.crypto.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Objects;

import javax.crypto.Cipher;

import org.bouncycastle.util.encoders.Hex;

import com.gitee.xqxyxchy.crypto.CryptoParam;
import com.gitee.xqxyxchy.crypto.ICryptoService;
import com.gitee.xqxyxchy.crypto.Pair;
import com.gitee.xqxyxchy.crypto.ParamKey;

public class RsaCrypto implements ICryptoService {

	private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
	private static final String KEY_ALGORITHM = "RSA";
	private static final String DEFAULT_ALGORITHMS = "RSA/ECB/PKCS1Padding";

	private static final String DEFAULT_KEY_SIZE = "2048";
	private static final String KEY_SIZE_1024 = "1024";

	/**
	 * RSA加密每次最大支持117个字节
	 */
	private static final int maxEncryptBlockSize117 = 117;
	/**
	 * RSA解密每次最大支持128个字节
	 */
	private static final int maxDecryptBlockSize128 = 128;
	/**
	 * RSA加密每次最大支持373个字节
	 */
	private static final int maxEncryptBlockSize245 = 245;
	/**
	 * RSA解密每次最大支持384个字节
	 */
	private static final int maxDecryptBlockSize256 = 256;

	@Override
	public String encrypt(String originData, CryptoParam... params) {
		String keySize = CryptoParam.Builder.create().get(ParamKey.KEY_SIZE.getKey(), DEFAULT_KEY_SIZE, params);
		String algorithms = CryptoParam.Builder.create().get(ParamKey.ALGORITHMS.getKey(), DEFAULT_ALGORITHMS, params);
		String publicKey = CryptoParam.Builder.create().get(ParamKey.PUBLIC_KEY.getKey(), params);
		if (DEFAULT_KEY_SIZE.equals(keySize)) {
			return encrypt(originData, algorithms, publicKey, maxEncryptBlockSize245);
		} else if (KEY_SIZE_1024.equals(keySize)) {
			return encrypt(originData, algorithms, publicKey, maxEncryptBlockSize117);
		} else {
			throw new RuntimeException("key size of " + keySize + " is not supported.");
		}
	}

	private String encrypt(String originData, String algorithms, String publicKey, int maxEncryptBlockSize) {
		ByteArrayOutputStream outputStream = null;
		try {
			Cipher cipher = Cipher.getInstance(algorithms, new org.bouncycastle.jce.provider.BouncyCastleProvider());
			cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey));
			byte[] inputData = originData.getBytes(DEFAULT_CHARSET);
			int inputLength = inputData.length;
			int offset = 0;
			// 分块加密
			outputStream = new ByteArrayOutputStream();
			while (inputLength - offset > 0) {
				byte[] block;
				if (inputLength - offset > maxEncryptBlockSize) {
					block = cipher.doFinal(inputData, offset, maxEncryptBlockSize);
				} else {
					block = cipher.doFinal(inputData, offset, inputLength - offset);
				}
				outputStream.write(block, 0, block.length);
				offset += maxEncryptBlockSize;
			}
			byte[] encryptedData = outputStream.toByteArray();
			return new String(Hex.toHexString(encryptedData));
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (Objects.nonNull(outputStream)) {
				try {
					outputStream.close();
				} catch (IOException e) {
				}
			}
		}
	}

	@Override
	public String decrypt(String encryptedData, CryptoParam... params) {
		String keySize = CryptoParam.Builder.create().get(ParamKey.KEY_SIZE.getKey(), DEFAULT_KEY_SIZE, params);
		String algorithms = CryptoParam.Builder.create().get(ParamKey.ALGORITHMS.getKey(), DEFAULT_ALGORITHMS, params);
		String privateKey = CryptoParam.Builder.create().get(ParamKey.PRIVATE_KEY.getKey(), params);
		if (DEFAULT_KEY_SIZE.equals(keySize)) {
			return decrypt(encryptedData, algorithms, privateKey, maxDecryptBlockSize256);
		} else if (KEY_SIZE_1024.equals(keySize)) {
			return decrypt(encryptedData, algorithms, privateKey, maxDecryptBlockSize128);
		} else {
			throw new RuntimeException("key size of " + keySize + " is not supported.");
		}
	}

	private String decrypt(String encryptedData, String algorithms, String privateKey, int maxDecryptBlockSize) {
		ByteArrayOutputStream outputStream = null;
		try {
			Cipher cipher = Cipher.getInstance(algorithms, new org.bouncycastle.jce.provider.BouncyCastleProvider());
			cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKey));
			byte[] decode = Hex.decode(encryptedData);
			int inputLength = decode.length;
			int offset = 0;
			outputStream = new ByteArrayOutputStream();
			while (inputLength - offset > 0) {
				byte[] block;
				if (inputLength - offset > maxDecryptBlockSize) {
					block = cipher.doFinal(decode, offset, maxDecryptBlockSize);
				} else {
					block = cipher.doFinal(decode, offset, inputLength - offset);
				}
				outputStream.write(block, 0, block.length);
				offset += maxDecryptBlockSize;
			}
			byte[] decryptedData = outputStream.toByteArray();
			return new String(decryptedData, DEFAULT_CHARSET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (Objects.nonNull(outputStream)) {
				try {
					outputStream.close();
				} catch (IOException e) {
				}
			}
		}
	}

	////////////////////////////////////////////////////////////////////

	public static PublicKey getPublicKey(String publicKey) throws Exception {
		byte[] bytes = publicKey.getBytes(DEFAULT_CHARSET);
		return getPublicKey(bytes);
	}

	private static PublicKey getPublicKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
		bytes = java.util.Base64.getDecoder().decode(bytes);
		X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
		return factory.generatePublic(spec);
	}

	public static PrivateKey getPrivateKey(String privateKey) throws Exception {
		byte[] bytes = privateKey.getBytes(DEFAULT_CHARSET);
		return getPrivateKey(bytes);
	}

	private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
		bytes = java.util.Base64.getDecoder().decode(bytes);
		PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
		return factory.generatePrivate(spec);
	}

	public static Pair<String, String> generateKey1024(String secret) throws Exception {
		return generateKey(secret, 1024);
	}

	public static Pair<String, String> generateKey2048(String secret) throws Exception {
		return generateKey(secret, 2048);
	}

	public static Pair<String, String> generateKey(String secret, int keySize) throws Exception {
		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
		SecureRandom secureRandom = new SecureRandom(secret.getBytes());
		keyPairGenerator.initialize(Math.max(1024, keySize), secureRandom);
		KeyPair keyPair = keyPairGenerator.genKeyPair();

		// 获取公钥并写出
		byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
		publicKeyBytes = java.util.Base64.getEncoder().encode(publicKeyBytes);

		// 获取私钥并写出
		byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
		privateKeyBytes = java.util.Base64.getEncoder().encode(privateKeyBytes);

		return Pair.of(new String(publicKeyBytes, DEFAULT_CHARSET), new String(privateKeyBytes, DEFAULT_CHARSET));
	}

}
